Justin Merrill

Engineer | Cloud | DevSecOps | SRE | K8s | GitOps | IaC | Data | APIs | BS | MBA


Optimum “Universal” Nginx1.18.+ Reverse-Proxy to Apache2.4+ Configuration For PHP7+ Web Applications (Including WordPress & WPMU) with Cloudflare SSL, DNS, Caching & More

I’ve been wanting to do this for a long time, now. I’ve been dabbling with a variety of ways to implement a template for using the Amazing Speed & Caching mechanisms of nginx for serving up static files & content that combines the superior Event MPM & PHP-FPM capabilities that Apache v2.4+ offers for crunching server-side processes (or something like FastCGI for Perl or FastCGI WSGI for Python).

There are a lot of different low-cost cloud infrastructure options that are becoming more mainstream, such as the low cost Vultr VPS & IONOS VPS S package for just $2 a month (It’s surprisingly awesome, too – The other larger IONOS VPS packages are the best price around the web right now, as well). If you prefer to play the “free trial for a year” games with the more complicated AWS & Azure service & stacks, you can also get some pretty cheap Cloud VMs that way, too (just brace yourself for the bill that’s coming next year when the trial is over & you’re married to the chosen platform).

When you combine the low-cost infrastructure options with services like Cloudflare DNS, & ZOHO suite, or some low-cost shared hosting solution combo product for managing data & email resources, you can really do a lot for very low cost.

A properly configured IONOS VPS S using nginx for serving (& caching) HTML, CSS, JavaScript, Images, Videos, etc with a load-balanced & reverse-proxy configuration to an Apache upstream to handle the PHP, Perl and/or Python combined with the power of Cloudflare DNS & its feature rich optimizations & caching… it’s not 2004 anymore! You can really serve up a TON of web requests with minimal resources & next to no cost at all for the static content itself. Prices start climbing higher when you get into things like network-intensive operations (eg: hosting or consuming huge amounts of API calls or maintaining a rapidly growing datastore of some kind) audio or video transcoding, sending or receiving large files or streaming. But most of these rather niche & specific operations can always be off-loaded to targeted resources or 3rd party services to cope with the heavy lifting. The majority of public-facing apps & websites are primarily for informational & transactional purposes. If you were to throw in a Redis cache or maybe a Varnish configuration in some cases, you could serve a mind blowing amount of content from the lowest cost “cloud resources”.

I’ve always got a thrill from tuning a system & squeezing every penny out of operational costs, so this guide is intended to be just another step in the direction of “cheap, fast AND good”.

Anyway. Enough talk. Let’s get to it.

NOTE: Eventually I’ll probably break everything down into stages and steps to follow. Apache 1st, nginx 2nd, whatever the database/datastore is 3rd, and possibly some kind of Docker or containerization setup for consistent deployment & code promotion environments with a CI/CD deployment strategy to wrap things up. For now, I’ll start by sharing the web server config file templates & eventually I’ll get around to breaking down each part that people can pick and chose how they want their environment(s).

Goals of this guide:

Have nginx serving up static content on port 80 and 443

Use Apache for all things PHP. Purely running PHP-FPM with Event MPM Module. (For now, we’ll leave out SSL with Apache since Nginx will be handling that. If we start adding multiple remote Apache backends, we will need to add SSL to those as well. Since Apache will be running on the same server instance as nginx in this scenario, we will assume all traffic to Apache (basically any requests for PHP) get sent to port 8080.

We will assume Debian / Ubuntu-style Apache Web Server is being used for this guide. There are quite a few nuances between Ubuntu & CentOS/RHEL/Fedora etc, but if you are choosing those flavors of Linux, I hope you understand these nuances and adjust your configuration accordingly. Since Debian-based Linux is the most popular, I’ll stick with that paradigm throughout the rest of this guide. I’ll also assume you’re working off of a “fresh install” with no programs running that will conflict with ports 22, 80, 443, 3306 or 8080.

Install apache2 with:

sudo apt-get update; sudo apt-get install apache2 -y;

next, let’s change the default port that Apache2 listens on

sudo nano /etc/apache2/ports.conf

Inside /etc/apache2/ports.conf

Save the /etc/apache2/ports.conf file & close it.

Since the default apache Virtualhost is also listening on port 80, let’s change that too in case in tries to cause us grief later on (sometimes it does).

sudo nano /etc/apache2/sites-enabled/000-default.conf

Inside the “file” /etc/apache2/sites-enabled/000-default.conf
(technically it’s a symlink to /etc/apache2/sites-available/000-default.conf)

Save & exit the “file” /etc/apache2/sites-enabled/000-default.conf

By now, I’m sure the more seasoned & experiences Devs & Engineers are annoyed with my step-by-step for the n00bz. So this is where we would add in our own “custom” VirtualHost directive for Apache (aka vhost).

Since we’re already in the apache directories, let’s do that now:

Read this carefully:

sudo nano /etc/apache/sites-available/domain-name-goes-here.com.conf

Notice the difference between sites-enabled & sites-available. Very important for later.
Also, obviously be sure to substitute your domain name (or even an IP address could potentially work) where you see: domain-name-goes-here.com

You may have notice my super long path of:

You’ll need to change that to fit where ever your path is to your website DocumentRoot, below. I will suggest that if you plan on having more than one domain name hosted from your super nifty future bling bling tricked out nginx+Apache+Cloudflare site that we’re building now, that you at least separate your directory paths by one level instead of the 3 that I have shown as an example here. This will help keep your clients and their individual websites/apps separated and also give “one level up” for the actual website document root path to store certain content and provide some security. Sure, there’s a case against nesting too deeply due to “traversing” but CPUs are blistering fast nowadays. I wouldn’t worry about it. And that’s coming from an optimization enthusiast.

Having a flexible directory structure to keep all your own projects as well as your client projects organized will probably help you out later down the road. You can also always symlink your directory tree to where ever you like on your server setup, too.

Example file and directory structure in a crude & terrible, quick & dirty diagram that I’ll have to replace later:

Anyway, edit your virtualhost .conf file to fit your directory layout.

Save the file & exit.

If you already have web content in your directory, cool. I’m assuming you do at least have an index.php or index.html/js/etc in your document root if you’re G enough to be looking into how to do a reverse proxy config in the first place. 😉

Since we’re assuming a Debian-style default Apache setup (CentOS & RHEL don’t do this “sites-enabled” thing by default) we need to add a symlink to “enable it”. We can do that in two ways:

Old school Linux SysAdmin style:

or, use the apachectl tools with:

sudo a2ensite domain-name-goes-here.com

There’s more to come with Apache, but for now, I’ll stop for the night and add the PHP7.4 install with all its modules and conf stuff, then onto nginx and the testing of the reverse proxy setup.

I will go ahead and dump off the template for the nginx vhost.conf here to get that out of the way:

EDIT: I wasn’t quit finished with this article that I started a few weeks ago, but I wanted to get it out there, even unpolished, for a spotlight on what I’ve been up to lately for helping make my case for an awesome opportunity I’ve been made aware of this week. 😉

More polish and content to come.

Leave a Reply

Your email address will not be published. Required fields are marked *