Apache reverse proxy
could do with filling out more detail
A reverse proxy allows you to front multiple websites from a single public IP address, act as a load balancer and potentially defuse otherwise dangerous cyber attacks. There are different solutions, the instructions here are for an Apache server-based solution. Basic requirements are Apache, mod_ssl and mod_proxy installed and enabled.
Installation
Install apache2
apt-get install apache2
File /etc/apache2/mods-available/proxy.conf should look like this:
<IfModule mod_proxy.c> # If you want to use apache2 as a forward proxy, uncomment the # 'ProxyRequests On' line and the <Proxy *> block below. # WARNING: Be careful to restrict access inside the <Proxy *> block. # Open proxy servers are dangerous both to your network and to the # Internet at large. # # If you only want to use apache2 as a reverse proxy/gateway in # front of some web application server, you DON'T need # 'ProxyRequests On'. ProxyRequests Off <Proxy *> AddDefaultCharset off Order deny,allow Deny from all </Proxy> # Enable/disable the handling of HTTP/1.1 "Via:" headers. # ("Full" adds the server version; "Block" removes all outgoing Via: headers) # Set to one of: Off | On | Full | Block #ProxyVia Off ProxyVia On ProxyPreserveHost On ProxyRequests Off ProxyTimeout 600 </IfModule>
Create file /etc/apache2/mods-available/proxy_http.conf and put this inside:
ProxyVia On ProxyPreserveHost On ProxyRequests Off <Proxy *> Order deny,allow Allow from all </Proxy>
Add these lines in /etc/apache2/apache2.conf to block paths that should not be visible
ProxyPass /.env ! ProxyPass /.git !
Enable these modules
a2enmod proxy a2enmod proxy_http a2enmod rewrite a2enmod headers a2enmod ssl a2enmod remoteip
Optional: By default apache only looks at .conf files from /etc/apache2/sites-enabled for sites. If you want it to look at all files edit this line in /etc/apache2/apache2.conf
IncludeOptional sites-enabled/*
On busier proxies you can modify /etc/apache2/mods-enabled/mpm_event.conf for a better performance. Here is an example:
<IfModule mpm_event_module> StartServers 5 MinSpareThreads 25 MaxSpareThreads 75 ThreadLimit 64 ThreadsPerChild 25 MaxRequestWorkers 250 MaxConnectionsPerChild 0 </IfModule>
And restart apache2
systemctl restart apache2
Failover proxy
Create a user on failover-proxy with a private key and one on proxy with the public key that would sync the data from proxy to failover-proxy.
On failover-proxy create script /opt/bin/proxy_sync:
#!/bin/bash #apache sync /usr/bin/rsync -rl --safe-links --delete --rsync-path="/usr/bin/sudo /usr/bin/rsync" <user>@<proxy-ip>:/etc/apache2/ /etc/apache2/ 2>&1 >> /var/log/proxy_sync.log #for letsencrypt certs /usr/bin/rsync -rl --safe-links --delete --rsync-path="/usr/bin/sudo /usr/bin/rsync" <user>@<proxy-ip>:/etc/letsencrypt/ /etc/letsencrypt/ 2>&1 >> /var/log/proxy_sync.log /usr/sbin/apache2ctl graceful
Make it executable
chmod +x /opt/bin/proxy_sync
Create a cron job for the script /etc/cron.d/proxy_sync:
*/15 * * * * <user> /opt/bin/proxy_sync
Keepalived
Keepalived is used to manage a floating IP.
To setup keepalived, install it on both servers:
sudo apt-get install keepalived
Copy the nagios check "check_http" to /usr/local/bin, from the /usr/lib/nagios/plugins of a server that has nagios-plugins installed (please don't install nagios-plugins on the reverse proxy, that package would install many dependencies).
Finally create the following /etc/keepalived/keepalived.conf on the master:
global_defs { notification_email { <email> } notification_email_from <email> smtp_server 127.0.0.1 } vrrp_script chk_apache { script "/usr/local/bin/check_http -H 127.0.0.1 -e 200" interval 3 # check every 2 seconds weight 2 # add 2 points of prio if OK } vrrp_instance floating_ip { interface ens3 state MASTER virtual_router_id 51 priority 101 authentication { auth_type PASS auth_pass SHAREDPASSWORD } virtual_ipaddress { <select-an-ip> } track_script { chk_apache } }
Create exactly the same file on the failover proxy, just change priority from 101 to 100.
Explanations of the apache check
The track_script "chk_apache" uses nagios' script check_http to ask a webpage to the local apache every three seconds - and checks for a 200 "OK" error code. If the test succeeds, it adds 2 points to the priority of the server.
The server with the highest priority gets the IP address. In effect, we would have the master with a priority of 101+2, and the failover with a priority of 100+2.
If the check fails on the master, its priority goes down to 100, and the IP migrates to the failover, which still has a priority of 100+2.
Adding a vhost
In Apache we use vhost declarations to define each reverse proxy FQDN. In Ubuntu/Debian systems these are found in /etc/apache2/sites-available, typically one per vhost using a suitably descriptive name. They can also be wrapped into a single file, or of course into the main apache conf file. As they are effectively (includes) of the Apache conf, every change requires an Apache restart to apply:
apache2ctl restart
In the Debian/Ubuntu model you also need to enable a site one it's been defined, which is done with a link to the /etc/apache2/sites-available/ file newly created:
cd /etc/apache2/sites-enabled ln -s ../sites-available/yournewvhost
This approach allows you to quickly and easily take a specific site offline if there's a problem, just by deleting the link in /etc/apache2/sites-enabled and restarting Apache.
Assuming your sites will be https from the proxy outwards, start with a 301 to force https:
<VirtualHost *:80> ServerName my.domain.name ServerAlias my.alias.domain Redirect 301 / https://my.domain.name ProxyPass / http://my.realserver.nameorIP/ ProxyPassReverse / http://my.realserve.nameorIP/ CustomLog /var/log/apache2/my.domain.name.access.log combined ErrorLog /var/log/apache2/my.domain.name.error.log </VirtualHost>
And then add an SSL entry
<VirtualHost *:443> ServerName my.domain.name SSLEngine on SSLCertificateFile /etc/apache2/ssl/mycertificate.crt SSLCertificateKeyFile /etc/apache2/ssl/mykey.key SSLCertificateChainFile /etc/apache2/ssl/myintermediatecertificateifneeded.crt ProxyPass / http://myrealserver.nameorIP/ ProxyPassReverse / http://my.domain.name/ ProxyPassReverse / http://myrealserver.nameorIP/ CustomLog /var/log/apache2/my.domain.name.access.log combined ErrorLog /var/log/apache2/my.domain.name.error.log </VirtualHost>
If you want the SSL proxy to also connect to the target as SSL, change the ProxyPass URLs appropriately and add to the vhost SSL (*:443) part
SSLProxyEngine On
If you run into issues with SSL connects to the target service with Apache log entries like
Error during SSL Handshake with remote server
then you may need additional settings in the vhost statement; try
SSLProxyVerify none SSLProxyCheckPeerCN off SSLProxyCheckPeerName off
and if that works selectively comment out to optimise (least settings for desired functionality).
Enabling SSL on a VM hosting a CMS behind a proxy
The CMS tries to detect if it's running on SSL automatically by checking some SERVER environment variables. When SSL is served from the proxy, the CMS can't detect it by normal means, so it needs a bit of help. In our case the solution is to edit the .htaccess file and add this directive:
<IfModule mod_env.c> SetEnv HTTPS on </IfModule>
This was the fix for both Joomla and Wordpress VMs
Maintenance Page
If you are working on a website and want to show a maintenance page for everyone except you, this can be done from the Apache vhost on the proxy.
<VirtualHost *:443> ServerName my.website.com ProxyPass / http://ip.add.re.ss/ ProxyPassReverse / http://ip.add.er.ss/ RewriteEngine On RewriteCond %{REMOTE_ADDR} !^my\.ip\.addr\.ess RewriteCond %{REQUEST_FILENAME} !/index.html RewriteCond %{REQUEST_FILENAME} !/css/app.css RewriteCond %{REQUEST_FILENAME} !/logo.png RewriteRule ^.*$ /var/www/maintenance/index.html [L] Header Set Cache-Control "max-age=0, no-store" CustomLog /var/log/apache2/my.website.com.access.log combined ErrorLog /var/log/apache2/my.website.com.error.log </VirtualHost>
Redirect source IP to VMs
On the vm vhost add/edit the following lines:
SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" forwarded ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined env=!forwarded CustomLog ${APACHE_LOG_DIR}/access.log forwarded env=forwarded
And enable 2 modules:
a2enmod remoteip && a2enmod headers
Redirect except one IP
Note: This works only for apache 2.4 onwards. For older versions of apache find something around RewriteEngine.
<If "%{REMOTE_ADDR} !='my.ip.add.re.ss'"> Redirect 301 / https://my.website.com </If>
Get client's real IP address
It is a well known problem that when you run an apache web server behind a proxy, you cannot get client real IP address. To solve this issue we just have to run next commands on each backend server:
sudo a2enmod remoteip sudo echo "RemoteIPHeader X-Forwarded-For" >> /etc/apache2/apache2.conf sudo sed -i "s/LogFormat \"%v:%p %h/LogFormat \"%v:%p %a/g" /etc/apache2/apache2.conf sudo sed -i "s/LogFormat \"%h/LogFormat \"%a/g" /etc/apache2/apache2.conf sudo apache2ctl graceful
How to reverse proxy WebSockets
Enable the websockets module if not already
a2enmod proxy_wstunnel
Add these lines before the ProxyPass lines
RewriteEngine On RewriteCond %{HTTP:Upgrade} =websocket [NC] RewriteRule /(.*) ws://172.17.16.17/$1 [P,L] RewriteCond %{HTTP:Upgrade} !=websocket [NC] RewriteRule /(.*) http://172.17.16.17/$1 [P,L]