Set up Nginx as Reverse Proxy Cache for WordPress

Share your love

Nginx is great for handling multiple simultaneous users without using a lot of server resources. Apache is extremely fast for processing PHP efficiently. Nginx is not great for processing PHP (in fact, Nginx cannot process PHP at all). Apache is not that efficient for handling multiple simultaneous users.

So, to get the best of the both worlds, I will show you how to set up Nginx as reverse proxy cache for Apache and additionally, will optimize it for WordPress.

I will be using AWS Lightsail for this tutorial but remember, the steps can be followed on any server. Also, make sure you replace example, example.conf or example.com with your own domain name.

1) Setting up a AWS Lightsail Instance

Make sure you have an active AWS account. Go to

https://lightsail.aws.amazon.com/ls/webapp/

and sign in with your AWS account. After that, you will be greeted with the Lightsail console as shown below (ignore the running instance as I am hosting this site on it).

Lightsail Console

Then, click on create instance and select your preferred region and zone and in the Instance Image section, click on Ubuntu 20.04 LTS and then select the instance site and click on Create Instance to create your Lightsail instance.

Lightsail Instance Creation

After creating your instance, SSH into it for the next steps.

2) Installing Apache and Nginx

Use the following command to update and upgrade the packages:

sudo apt-get update && sudo apt-get upgrade

After it completes, use the following command to install Apache:

sudo apt-get install apache2

Now, we need to configure Apache to listen on a different port to avoid clashing with Nginx. Do this by opening the ports.conf file of Apache and changing the port to 8081:

sudo nano /etc/apache2/ports.conf

Make sure it looks like this (after the change):

Listen 8081

Exit and save the file by using Ctrl + x followed by y and then press Enter to save it.

To apply the new port configuration, restart Apache with the following command:

sudo service apache2 restart

Now, install Nginx with:

sudo apt-get install nginx

Once the installation is complete, check the status of both of the servers:

sudo service apache2 status
sudo service nginx status

Make sure both of them are in Running state.

3) Set up Virtual Hosts in Apache

First, for staying organized, make a new folder called wordpress with the following command:

sudo mkdir -p /var/www/wordpress

Now, we need to set up the virtual host.

Remove the default site first:

sudo a2dissite 000-default

Create a new configuration for your site:

sudo nano /etc/apache2/sites-available/example.conf

Paste the following in the file that you created:

<VirtualHost *:8081>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/wordpress
   <FilesMatch \.php$>
        SetHandler "proxy:unix:/var/run/php/php7.4-fpm.sock|fcgi://localhost"
    </FilesMatch>

    <Directory />
        Options FollowSymLinks
        AllowOverride None
    </Directory>

    <Directory /var/www/wordpress>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Order allow,deny
        allow from all
        Require all granted
    </Directory>
</VirtualHost>

Save the file and enable the new configuration:

sudo a2ensite example.conf

Enable the Rewrite module:

sudo a2enmod rewrite

Restart Apache:

sudo service apache2 restart

4) Configuring Nginx

Ubuntu ships with the ufw firewall. Make sure to enable HTTP and HTTPS ports in that. You can do that with the following command:

sudo ufw allow 'Nginx Full'

Next, open the nginx.conf file:

sudo nano /etc/nginx/nginx.conf

Paste the following in that file:

user www-data;
worker_processes 1;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 768;
        multi_accept on;
}

http {
#proxy_cache_path /usr/share/nginx/fastcgi_cache levels=1:2 keys_zone=STATIC:100m max_size=10g inactive=60m use_temp_path=off;
#proxy_cache_key "$scheme$request_method$host$request_uri";
#proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=edge-cache:10m inactive=20m max_size=1g;
#proxy_temp_path /var/cache/nginx/tmp;
#    proxy_cache_path  /data/nginx/cache  levels=1:2    keys_zone=STATIC:10m  inactive=24h  max_size=1g;
    proxy_cache_path  /usr/share/nginx/cache  levels=1:2    keys_zone=STATIC:10m  inactive=24h  max_size=1g;
        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 15;
        types_hash_max_size 2048;
        server_tokens off;
        client_max_body_size 128m;
        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;

        # gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
        server {
                listen 80 default_server;
                listen [::]:80 default_server;
                server_name _;
                return 444;
        }
}


#mail {
#       # See sample authentication script at:
#       # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
#       # auth_http localhost/auth.php;
#       # pop3_capabilities "TOP" "USER";
#       # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
#       server {
#               listen     localhost:110;
#               protocol   pop3;
#               proxy      on;
#       }
#
#       server {
#               listen     localhost:143;
#               protocol   imap;
#               proxy      on;
#       }
#}

To make sure Nginx serves PHP correctly, set fastcgi_params directive. Else, you will face issues. Do this by opening the file:

sudo nano /etc/nginx/fastcgi_params

And the add the following line at the end of the file:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

Then save and exit the file.

Now, remove the default Nginx server configs:

sudo rm /etc/nginx/sites-available/default
sudo rm /etc/nginx/sites-enabled/default

Restart Nginx

sudo service nginx restart

5) Set up Nginx as Reverse Proxy Cache

Create the server block for your site:

sudo nano /etc/nginx/sites-available/example.conf

Paste the following:

 server {
    listen [::]:80;
    listen 80;

#        add_header X-Cache-Status $upstream_cache_status;
#        add_header X-Handled-By $proxy_host;
#        proxy_pass_request_headers on;
#        proxy_cache            STATIC;
#        proxy_buffering        on;
#        proxy_cache_valid      200  1d;
#        proxy_cache_use_stale  error timeout invalid_header updating
#                                   http_500 http_502 http_503 http_504;

        proxy_cache STATIC;
        add_header X-Cache-Status $upstream_cache_status;
        add_header X-Handled-By $proxy_host;
        proxy_pass_request_headers on;
        proxy_cache_revalidate on;
            proxy_cache_valid      200  1d;
            proxy_cache_use_stale  error timeout invalid_header updating
                                   http_500 http_502 http_503 http_504;

        proxy_read_timeout 180s;

set $skip_cache 0;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
    set $skip_cache 1;
}
if ($query_string != "") {
    set $skip_cache 1;
}

# Don't cache uris containing the following segments
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|^/feed/*|/tag/.*/feed/*|index.php|/.*sitemap.*\.(xml|xsl)") {
    set $skip_cache 1;
}

# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
    set $skip_cache 1;
}

location ~ /purge(/.*) {
      proxy_cache_purge STATIC "$scheme$request_method$host$1";
}

    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;

    root /var/www/wordpress;
    index index.php;

    location / {
        try_files $uri @apache;
    }

    location ~ ^/\.user\.ini {
        deny all;
    }

        location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                expires max;
                log_not_found off;
        }


    location ~*  \.(svg|svgz)$ {
        types {}
        default_type image/svg+xml;
    }

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    location @apache {
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_pass http://127.0.0.1:8081;
    }

    location ~[^?]*/$ {
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_pass http://127.0.0.1:8081;
    }

    location ~ \.php$ {
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_pass http://127.0.0.1:8081;
    }

    location ~/\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

server {
    listen [::]:443 ssl http2;
    listen 443 ssl http2;

  # Rocket-Nginx configuration
  include rocket-nginx/default.conf;

        proxy_cache STATIC;
        add_header X-Cache-Status $upstream_cache_status;
        add_header X-handled-By $proxy_host;
        proxy_pass_request_headers on;
        proxy_cache_revalidate on;
            proxy_cache_valid      200  1d;
            proxy_cache_use_stale  error timeout invalid_header updating
                                   http_500 http_502 http_503 http_504;

        proxy_read_timeout 180s;

set $skip_cache 0;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
    set $skip_cache 1;
}
if ($query_string != "") {
    set $skip_cache 1;
}

# Don't cache uris containing the following segments
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|^/feed/*|/tag/.*/feed/*|index.php|/.*sitemap.*\.(xml|xsl)") {
    set $skip_cache 1;
}

# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
    set $skip_cache 1;
}

location ~ /purge(/.*) {
      proxy_cache_purge STATIC "$scheme$request_method$host$1";
}

    server_name www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    root /home/username/example.com/public/;
    index index.html index.php;

    return 301 https://example.com$request_uri;
}


server {
    listen [::]:443 ssl http2;
    listen 443 ssl http2;

  # Rocket-Nginx configuration
  include rocket-nginx/default.conf;


        proxy_cache STATIC;
        add_header X-Cache-Status $upstream_cache_status;
        add_header X-Handled-By $proxy_host;
        proxy_pass_request_headers on;
        proxy_cache_revalidate on;
            proxy_cache_valid      200  1d;
            proxy_cache_use_stale  error timeout invalid_header updating
                                   http_500 http_502 http_503 http_504;

        proxy_read_timeout 180s;

set $skip_cache 0;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
    set $skip_cache 1;
}
if ($query_string != "") {
    set $skip_cache 1;
}

# Don't cache uris containing the following segments
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|^/feed/*|/tag/.*/feed/*|index.php|/.*sitemap.*\.(xml|xsl)") {
    set $skip_cache 1;
}

# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
    set $skip_cache 1;
}

location ~ /purge(/.*) {
       proxy_cache_purge STATIC "$scheme$request_method$host$1";
}

    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    root /var/www/wordpress;
    index index.php;

    location / {
        try_files $uri @apache;
    }

    location ~ ^/\.user\.ini {
        deny all;
    }

    location ~*  \.(svg|svgz)$ {
        types {}
        default_type image/svg+xml;
    }

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

        location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                expires max;
                log_not_found off;
        }


    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    location @apache {
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_pass http://127.0.0.1:8081;
    }

    location ~[^?]*/$ {
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_pass http://127.0.0.1:8081;
    }

    location ~ \.php$ {
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        proxy_pass http://127.0.0.1:8081;
    }

    location ~/\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

Then save and exit the file.

Enable the new config:

sudo ln -s /etc/nginx/sites-available/example.conf /etc/nginx/sites-enabled/example.conf

Check the configuration and restart Nginx:

sudo nginx -t
sudo service nginx restart

Now, Nginx is working as a reverse proxy cache for Apache.

6) Installing and configuring PHP and some Modules

Use the following command to install PHP and the required modules for WordPress:

sudo apt install php7.4 php7.4-fpm libapache2-mod-fcgid php7.4-common php7.4-mysql php7.4-xml php7.4-xmlrpc php7.4-curl php7.4-gd php7.4-imagick php7.4-cli php7.4-dev php7.4-imap php7.4-mbstring php7.4-opcache php7.4-soap php7.4-zip php7.4-redis php7.4-intl unzip -y

Then, open the php.ini file and modify some values:

sudo nano /etc/php/7.4/fpm/php.ini

Change the following to the values shown below:

upload_max_filesize = 32M 
post_max_size = 48M 
memory_limit = 256M 
max_execution_time = 600 
max_input_vars = 3000 
max_input_time = 1000

Save the file and restart Apache and PHP-FPM

sudo service apache2 restart
sudo service php7.4-fpm restart

7) Configuring Apache to use PHP-FPM

By default, Apache uses MOD-PHP which is slower than PHP-FPM. We have already installed libapache2-mod-fcgid in the previous section. Now, we need to change the PHP handler of Apache.

Use the following command:

sudo a2enmod actions fcgid alias proxy_fcgi

In one of the previous section, we have already set Apache to use the fcgi handler.

Restart Apache and PHP-FPM

sudo service restart apache2
sudo service restart php7.4-fpm

Now, Apache is using fcgi for PHP.

8) Install free Let’s Encrypt SSL

HTTPS is a major ranking factor for every search engine. Enabling HTTPS gives us access to HTTP/2, which is a significant improvement from HTTP/1.1.

Use the following commands for installing certbot:

sudo apt-get install certbot python3-certbot-nginx

Now, run the following command for issuing the certificate (make sure to point your domain to your server IP before doing entering this command. Else, verification will fail and SSL certificate won’t be issued):

sudo certbot --nginx certonly

Then enter your email and select the domains for which you want the certificate to be issued (hit Enter) to select all of them.

For renewing the certificates, you need to run a cronjob. Open crontab:

sudo crontab -e

Add the following line at the end of the file:

0 0,12 * * * certbot renew >/dev/null 2>&1

Save and exit the file.

Now, your server has a valid ssl certificate. All of the ssl and https configuration is already done in the files above.

9) Installing and Configuring WordPress

I have some guides about installing WordPress. For this tutorial, you can follow this guide: How to Install WordPress on LAMP Stack.

After following the entire guide, add the following lines to the top of the wp-config.php file, just below <?php:

define('FORCE_SSL_ADMIN', true);
if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
     $_SERVER['HTTPS']='on';

This will fix the unlimited redirects error.

Conclusion

By following this tutorial, you have learnt how to set up Nginx as reverse proxy cache for Apache to get the performance of Nginx and the efficiency of PHP file handling of Apache.

If you have any queries or suggestions, please let me know in the comments down below.

Share your love
Rahul Biswas
Rahul Biswas

I am the founder of WPSteam. I am currently doing my B.Tech in Electronics & Telecommunication Engineering from KIIT University, Bhubaneshwar, India

Articles: 89

Leave a Reply

%d bloggers like this: