Forums

Using a Cloudflare load balancer across multiple app instances for redundancy

Is there a way to set up a Cloudflare load balancer across 2 or more Pythonanywhere web apps?

I have successfully set up a Cloudflare load balancer with a Pythonanywhere US app and Pythonanywhere EU app. Here's how it is currently set up:

  1. Create duplicate web apps on both the US and EU instances of Pythonanywhere, both named app.mydomain.com
  2. Create CNAME DNS records for the two web apps: primary.mydomain.com > webapp-{us-app-id}.pythonanywhere.com, backup.mydomain.com > webapp-{eu-app-id}.eu.pythonanywhere.com
  3. Create two Cloudflare load balancer pools: endpoint addresses: primary.mydomain.com, backup.mydomain.com and add a Host header to both set to app.mydomain.com
  4. Create the Cloudflare load balancer pointed to app.mydomain.com, with the primary pool set to receive 100% of traffic and the backup pool as the fallback pool.
  5. Create Origin SSL certificate in Cloudflare and add it to both Pythonanywhere apps as a custom SSL certificate.

This setup works exactly as planned, with the primary, US-based web app receiving 100% of traffic as long as it is healthy. Cloudflare pings both servers every 60 seconds and automatically begins routing traffic to the backup only if the US server is down.

Because of it's physical location and distance from the database, the EU server is noticeably slower at responding to requests. I want to set up a second US-based instance as a backup (or potentially to share to load of the primary server), while keeping the EU server as the fallback for both US servers. Pythonanywhere enforces a limit that only one web app on their platform can have the same domain, preventing me from using this configuration for two US-based apps. Has anyone figured out a way to set this up?

I figured out how to make it work. The above steps work for US-EU failover. However, if you want US-US or EU-EU redundancy pools using Cloudflare Load Balancer, here are the steps to make it work:

  1. Set the names for each web app to something unique (e.g., app-1.mydomain.com, app-2.mydomain.com, etc.)
  2. Set up CNAME DNS records as instructed in Pythonanywhere for each web app, pointing to webapp-{app_id}.pythonanywhere.com
  3. Create Cloudflare load balancer pools spreading the server load how you prefer. I set up 2 pools, a primary and a backup. In the primary pool, I put all of my Pythoanywhere US apps as separate endpoints. Set the endpoint address and Host header to the app name (e.g., app-1.mydomain.com) for each app.
  4. Create a Transform Rule in Cloudflare for your public-facing URL (this was the key to making it work). The rule is set up as follows: If Hostname = app.mydomain.com: Then, Set dynamic, Header Name: X-Forwarded-Host = http.host and Set dynamic, Header Name: X-Original-Host = http.host
  5. In my application code (Flask), I set up a handler to read these headers and set the HTTP_HOST to this value (code example below). It seems that Pythonanywhere doesn't send the X-Forwarded-Host header, which is why I added the custom X-Original-Host header.
  6. Set up the Cloudflare Load Balancer with the following settings: Session Affinity=On, By Cloudflare cookie only; Session TTL and Endpoint drain duration can be whatever you want; Zero downtime failover=Sticky; Failover across pools=On; Add Primary and Backup Pools and set Backup Pool as Fallback Pool; Traffic Steering=Off (only route pools in failover order);
  7. Make sure you use Origin SSL Certificates generated in Cloudflare on the Pythonanywhere apps.

Here's the code I added to my app to ensure urls generated using the url_for() function have the correct host.

from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)
app.config['PREFERRED_URL_SCHEME'] = 'https'
class HostFixMiddleware:
def __init__(self, app):
    self.app = app

def __call__(self, environ, start_response):
    host = environ.get('HTTP_X_FORWARDED_HOST') or environ.get('HTTP_X_ORIGINAL_HOST')
    if host:
        environ['HTTP_HOST'] = host
    return self.app(environ, start_response)

app.wsgi_app = HostFixMiddleware(app.wsgi_app)