Apache and Nginx are two popular open-source web servers often used with PHP. It can be useful to run both of them on the same virtual machine when hosting multiple websites which have varied requirements. The general solution for running two web servers on a single system is to either use multiple IP addresses or use different port numbers.
Servers which have both IPv4 and IPv6 addresses can be configured to serve Apache sites on one protocol and Nginx sites on the other, but this isn’t currently practical, as IPv6 adoption by ISPs is still not widespread. Having a different port number for the second web server is another solution, but sharing URLs with port numbers isn’t always ideal.
In this post, we will configure Nginx as both a web server and as a reverse proxy for Apache – all on a single server.
Depending on the web application, code changes might be required to keep Apache reverse-proxy-aware, especially when SSL sites are configured. To avoid this, we will install an Apache module called mod_rpaf
which rewrites certain environment variables so it appears that Apache is directly handling requests from web clients.
We will host four domain names on one server. Two will be served by Nginx: example.com
(the default virtual host) and sample.org
. The remaining two, foobar.net
and test.io
, will be served by Apache. We’ll also configure Apache to serve PHP applications using PHP-FPM
, which offers better performance over mod_php
.
Reference:
Config Apache & PHP-FPM
First:
1 | sudo apt install apache2 php-fpm |
In this step we will change Apache’s port number to 8080 and configure it to work with PHP-FPM
using the mod_fastcgi
module. Rename Apache’s ports.conf
configuration file:
1 | sudo mv /etc/apache2/ports.conf /etc/apache2/ports.conf.default |
Create a new ports.conf
file with the port set to 8080:
1 | echo "Listen 8080" | sudo tee /etc/apache2/ports.conf |
Note:
Web servers are generally set to listen on
127.0.0.1:8080
when configuring a reverse proxy but doing so would set the value of PHP’s environment variableSERVER_ADDR
to the loopback IP address instead of the server’s public IP. Our aim is to set up Apache in such a way that its websites do not see a reverse proxy in front of it. So, we will configure it to listen on 8080 on all IP addresses.
Next we’ll create a virtual host file for Apache. The <VirtualHost>
directive in this file will be set to serve sites only on port 8080.
Disable the default virtual host:
1 | sudo a2dissite 000-default |
Then create a new virtual host file, using the existing default site:
1 | sudo cp /etc/apache2/sites-available/000-default.conf \ |
Now open the new configuration file:
1 | sudo vim /etc/apache2/sites-available/001-default.conf |
Change the listening port to 8080:
1 | # Content |
Save the file and activate the new configuration file:
1 | sudo a2ensite 001-default |
Then reload Apache:
1 | sudo systemctl reload apache2 |
Verify that Apache is now listening on 8080:
1 | sudo netstat -tlpn |
The output should look like the following example, with apache2 listening on 8080:
1 | # Output |
Once we verify that Apache is listening on the correct port, we can configure support for PHP and FastCGI
.
Use mod_fastcgi
Apache serves PHP pages using mod_php
by default, but it requires additional configuration to work with PHP-FPM.
Note: If we are trying on an existing installation of LAMP with
mod_php
, disable it first with:
sudo a2dismod php7.3
We will be adding a configuration block for mod_fastcgi
which depends on mod_action
. mod_action
is disabled by default, so we first need to enable it:
1 | sudo a2enmod actions |
Rename the existing FastCGI configuration file:
1 | sudo mv /etc/apache2/mods-enabled/fastcgi.conf \ |
Create a new configuration file:
1 | sudo vim /etc/apache2/mods-enabled/fastcgi.conf |
Add the following directives to the file to pass requests for .php
files to the PHP-FPM UNIX socket:
1 | <!-- Content --> |
Save the changes and do a configuration test:
1 | sudo apachectl -t |
Reload Apache if Syntax OK is displayed:
1 | sudo systemctl reload apache2 |
If we see the warning:
Could not reliably determine the server’s fully qualified domain name, using 127.0.1.1. Set the ‘ServerName’ directive globally to suppress this message.
We can safely ignore it for now. We’ll configure server names later.
Now let’s make sure we can serve PHP from Apache.
Verify PHP Functionality
Let’s make sure that PHP works by creating a phpinfo()
file and accessing it from a web browser.
Create the file /var/www/html/info.php
which contains a call to the phpinfo
function:
1 | echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php |
To see the file in a browser, go to localhost:8080/info.php
. This will give us a list of the configuration settings PHP is using. We’ll see output similar to this:
At the top of the page, check that Server API says FPM/FastCGI. About two-thirds of the way down the page, the PHP Variables section will tell we the SERVER_SOFTWARE is Apache on Ubuntu. These confirm that mod_fastcgi is active and Apache is using PHP-FPM to process PHP files.
Create Virtual Hosts for Apache
Let’s create Apache virtual host files for the domains foobar.net
and test.io
. To do that, we’ll first create document root directories for both sites and place some default files in those directories so we can easily test our configuration.
First, create the document root directories:
1 | sudo mkdir -v /var/www/foobar.net /var/www/test.io |
Then create an index file for each site:
1 | echo "<h1>Foo Bar</h1>" | sudo tee /var/www/foobar.net/index.html |
Then create a phpinfo()
file for each site so we can test that PHP is configured properly.
1 | echo "<?php phpinfo(); ?>" | sudo tee /var/www/foobar.net/info.php |
Now create the virtual host file for the foobar.net
domain:
1 | sudo vim /etc/apache2/sites-available/foobar.net.conf |
Add the following code to the file to define the host:
1 | <!-- Content --> |
The line AllowOverride All
enables .htaccess
support. Save and close the file. Then create a similar configuration for test.io
. First create the file:
1 | sudo vim /etc/apache2/sites-available/test.io.conf |
Then add the configuration to the file:
1 | <!-- Content --> |
Now that both Apache virtual hosts are set up, enable the sites using the a2ensite
command. This creates a symbolic link to the virtual host file in the sites-enabled directory:
1 | sudo a2ensite foobar.net |
Check Apache for configuration errors again:
1 | sudo apachectl -t |
We’ll see Syntax OK displayed if there are no errors. If we see anything else, review the configuration and try again.
Reload Apache to apply the changes once our configuration is error-free:
1 | sudo systemctl reload apache2 |
To confirm the sites are working, open http://foobar.net:8080
and http://test.io:8080
in the browser and verify that each site displays its index.html
file.
We’ll see the following results:
Also, ensure that PHP is working by accessing the info.php
files for each site. Visit http://foobar.net:8080/info.php
and http://test.io:8080/info.php
in the browser.
We now have two websites hosted on Apache at port 8080. Let’s configure Nginx next.
Install & Config Nginx
In this step we’ll install Nginx and configure the domains example.com
and sample.org
as Nginx’s virtual hosts. After install Nginx, remove the default virtual host’s symlink since we won’t be using it any more:
1 | sudo rm /etc/nginx/sites-enabled/default |
We’ll create our own default site later (example.com
).
Now we’ll create virtual hosts for Nginx using the same procedure we used for Apache. First create document root directories for both the websites:
1 | sudo mkdir -v /usr/share/nginx/example.com /usr/share/nginx/sample.org |
We’ll keep the Nginx web sites in /usr/share/nginx
, which is where Nginx wants them by default. We could put them under /var/www/html
with the Apache sites, but this separation may help you associate sites with Nginx.
As you did with Apache’s virtual hosts, create index and phpinfo()
files for testing after setup is complete:
1 | echo "<h1>Example.com</h1>" | sudo tee /usr/share/nginx/example.com/index.html |
Now create a virtual host file for the domain example.com
:
1 | sudo vim /etc/nginx/sites-available/example.com |
Nginx calls server {. . .} areas of a configuration file server blocks. Create a server block for the primary virtual host, example.com
. The default_server
configuration directive makes this the default virtual host which processes HTTP requests which do not match any other virtual host.
1 | # Content |
Now create a virtual host file for Nginx’s second domain, sample.org
:
1 | sudo vim etc/nginx/sites-available/sample.org |
Add the following to the file:
1 | # Content |
Then enable both sites by creating symbolic links to the sites-enabled directory:
1 | sudo ln -s /etc/nginx/sites-available/example.com \ |
Then test the Nginx configuration to ensure there are no configuration issues:
1 | sudo nginx -t |
Then reload Nginx if there are no errors:
1 | sudo systemctl reload nginx |
Now access the phpinfo()
file of our Nginx virtual hosts in a web browser by visiting http://example.com/info.php
and http://sample.org/info.php
. Look under the PHP Variables sections again.
[“SERVER_SOFTWARE”] should say nginx, indicating that the files were directly served by Nginx. [“DOCUMENT_ROOT”] should point to the directory we created earlier in this step for each Nginx site.
At this point, we have installed Nginx and created two virtual hosts. Next we will configure Nginx to proxy requests meant for domains hosted on Apache.
Nginx for Apache’s Virtual Hosts
Let’s create an additional Nginx virtual host with multiple domain names in the server_name directives. Requests for these domain names will be proxied to Apache.
Create a new Nginx virtual host file to forward requests to Apache:
1 | sudo vim /etc/nginx/sites-available/apache |
Add the following code block which specifies the names of both Apache virtual host domains and proxies their requests to Apache. Remember to use the public IP address in proxy_pass
:
1 | # Content |
Save the file and enable this new virtual host by creating a symbolic link:
1 | sudo ln -s /etc/nginx/sites-available/apache /etc/nginx/sites-enabled/apache |
Test the configuration to ensure there are no errors:
1 | sudo nginx -t |
If there are no errors, reload Nginx:
1 | sudo systemctl reload nginx |
Open the browser and access the URL http://foobar.net/info.php in the browser. Scroll down to the PHP Variables section and check the values displayed.
The variables SERVER_SOFTWARE and DOCUMENT_ROOT confirm that this request was handled by Apache. The variables HTTP_X_REAL_IP and HTTP_X_FORWARDED_FOR were added by Nginx and should show the public IP address of the computer we’re using to access the URL.
We have successfully set up Nginx to proxy requests for specific domains to Apache. Next, let’s configure Apache to set the REMOTE_ADDR
variable as if it were handling these requests directly.
Install & Config mod_rpaf
Now we will install an Apache module called mod\_rpaf
which rewrites the values of REMOTE_ADDR
, HTTPS and HTTP_PORT based on the values provided by a reverse proxy. Without this module, some PHP applications would require code changes to work seamlessly from behind a proxy. This module is present in Ubuntu’s repository as libapache2-mod-rpaf
but is outdated and doesn’t support certain configuration directives. Instead, we will install it from source.
Install the packages needed to build the module:
1 | sudo apt install unzip build-essential apache2-dev |
Download the latest stable release from GitHub:
1 | wget https://github.com/gnif/mod_rpaf/archive/stable.zip |
Extract the downloaded file:
1 | unzip stable.zip |
Change into the new directory containing the files:
1 | cd mod_rpaf-stable |
Compile and install the module:
1 | make |
Next, create a file in the mods-available directory which will load the rpaf
module:
1 | sudo vim /etc/apache2/mods-available/rpaf.load |
Add the following code to the file to load the module:
1 | # Content |
Create another file in this directory called rpaf.conf
which will contain the configuration directives for mod_rpaf
:
1 | sudo vim /etc/apache2/mods-available/rpaf.conf |
Add the following code block to configure mod_rpaf
, making sure to specify the IP address of the server:
1 | <!-- Content --> |
Here’s a brief description of each directive. See the mod_rpaf
README file for more information.
RPAF_Header
- The header to use for the client’s real IP address.RPAF_ProxyIPs
- The proxy IP to adjust HTTP requests for.RPAF_SetHostName
- Updates the vhost name soServerName
andServerAlias
work.RPAF_SetHTTPS
- Sets the HTTPS environment variable based on the value contained inX-Forwarded-Proto
.RPAF_SetPort
- Sets the SERVER_PORT environment variable. Useful for when Apache is behind a SSL proxy.
Save rpaf.conf
and enable the module:
1 | sudo a2enmod rpaf |
This creates symbolic links of the files rpaf.load
and rpaf.conf
in the mods-enabled directory. Now do a configuration test:
1 | sudo apachectl -t |
Reload Apache if there are no errors:
1 | sudo systemctl reload apache2 |
Access the phpinfo()
pages http://foobar.net/info.php and http://test.io/info.php in our browser and check the PHP Variables section. The REMOTE_ADDR
variable will now also be that of the local computer’s public IP address.
Now let’s set up TLS/SSL encryption for each site.
HTTPS with Let’s Encrypt
In this step we will configure TLS/SSL certificates for both the domains hosted on Apache. We’ll obtain the certificates through Let’s Encrypt. Nginx supports SSL termination so we can set up SSL without modifying Apache’s configuration files. The mod_rpaf
module ensures the required environment variables are set on Apache to make applications work seamlessly behind a SSL reverse proxy.
First we will separate the server {…} blocks of both the domains so that each of them can have their own SSL certificates.
1 | sudo vim /etc/nginx/sites-available/apache |
Modify the file so that it looks like this, with foobar.net
and test.io
in their own server blocks:
1 | # Content |
We’ll use Certbot to generate our TLS/SSL certificates. Its Nginx plugin will take care of reconfiguring Nginx and reloading the config whenever necessary.
First, add the official Certbot repository:
1 | sudo add-apt-repository ppa:certbot/certbot |
Press ENTER when prompted to confirm that we want to add the new repository. Then update the package list to pick up the new repository’s package information:
1 | sudo apt update |
Then install Certbot’s Nginx package with apt:
1 | sudo apt install python-certbot-nginx |
Once it’s installed, use the certbot command to generate the certificates for foobar.net
and www.foobar.net:
1 | sudo certbot --nginx -d foobar.net -d www.foobar.net |
This command tells Certbot to use the Nginx plugin, using -d to specify the names we’d like the certificate to be valid for.
If this is our first time running certbot, we will be prompted to enter an email address and agree to the terms of service. After doing so, certbot will communicate with the Let’s Encrypt server, then run a challenge to verify that we control the domain we’re requesting a certificate for.
Next, Certbot will ask how we’d like to configure our HTTPS settings:
1 | # Output |
Select your choice, then press ENTER. The configuration will be updated, and Nginx will reload to pick up the new settings. Now execute the command for the second domain:
1 | sudo certbot --nginx -d test.io -d www.test.io |
Access one of Apache’s domains in the browser using the https:// prefix; visit https://foobar.net/info.php and we’ll see this:
Look in the PHP Variables section. The variable SERVER_PORT
has been set to 443 and HTTPS set to on, as though Apache was directly accessed over HTTPS. With these variables set, PHP applications do not have to be specially configured to work behind a reverse proxy.
Now let’s disable direct access to Apache.
Block Direct Access to Apache
Since Apache is listening on port 8080 on the public IP address, it is accessible by everyone. It can be blocked by working the following IPtables
command into your firewall rule set.
1 | sudo iptables -I INPUT -p tcp --dport 8080 ! \ |
Be sure to use your server’s IP address in place of the example in red. Once port 8080 is blocked in your firewall, test that Apache is unreachable on it. Open your web browser and try accessing one of Apache’s domain names on port 8080. For example: http://example.com:8080
The browser should display an Unable to connect
or Webpage is not available
error message. With the IPtables tcp-reset
option in place, an outsider would see no difference between port 8080 and a port that doesn’t have any service on it.
Note:
IPtables rules do not survive a system reboot by default. There are multiple ways to preserve IPtables rules, but the easiest is to use iptables-persistent in Ubuntu’s repository. Explore this article to learn more about how to configure IPTables.
Now let’s configure Nginx to serve static files for the Apache sites.
Serve Static Files with Nginx
When Nginx proxies requests for Apache’s domains, it sends every file request for that domain to Apache. Nginx is faster than Apache in serving static files like images, JavaScript and style sheets. So let’s configure Nginx’s apache virtual host file to directly serve static files but send PHP requests on to Apache.
Open the file:
1 | sudo vim /etc/nginx/sites-available/apache |
You’ll need to add two additional location blocks to each server block, as well as modify the existing location sections. In addition, you’ll need to tell Nginx where to find the static files for each site.
If you’ve decided not to use SSL and TLS certificates, modify your file so it looks like this:
1 | # Content |
If you also want HTTPS to be available, use the following configuration instead:
1 | # Content |
The try_files
directive makes Nginx look for files in the document root and directly serve them. If the file has a .php
extension, the request is passed to Apache. Even if the file is not found in the document root, the request is passed on to Apache so that application features like permalinks work without problems.
Warning:
The location
~ /.ht
directive is very important; this prevents Nginx from serving the contents of Apache configuration files like.htaccess
and.htpasswd
which contain sensitive information.
Save the file and perform a configuration test:
1 | sudo nginx -t |
Reload Nginx if the test succeeds:
1 | sudo service nginx reload |
To verify things are working, you can examine Apache’s log files in /var/log/apache2
and see the GET requests for the info.php
files of test.io
and foobar.net
. Use the tail command to see the last few lines of the file, and use the -f switch to watch the file for changes:
1 | sudo tail -f /var/log/apache2/other_vhosts_access.log |
Now visit http://test.io/info.php in your browser and then look at the output from the log. You’ll see that Apache is indeed replying:
1 | # Output |
Then visit the index.html
page for each site and you won’t see any log entries from Apache. Nginx is serving them.
With this setup, Apache will not be able to restrict access to static files. And access control for static files would need to be configured in Nginx’s apache virtual host file.