What is going on when I set app.wsgi_app = ProxyFix(app.wsgi_app) when running a Flask app on gunicorn?

19k Views Asked by At

I built a basic web app using Flask, and was able to run it from a virtual machine using its native http server. I quickly realized that with this set up, requests are blocking (I couldn't make concurrent requests for resources; any new request would wait until earlier requests had finished), and decided to try gunicorn to run the app to solve this problem. I followed the documentation, specifically running with this line:

gunicorn -w 4 -b 127.0.0.1:4000 myproject:app 

However, it failed to boot doing just this, and complained that there was no WSGI app. Poking around the internet, I found that a number of people had posted examples including the following:

from werkzeug.contrib.fixers import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)

I added that, and it resolved my problem. I am confused though because this is apparently meant to solve a problem serving behind an HTTP proxy, but would the addition of gunicorn impose an HTTP proxy? Or was I always behind a proxy, and it just didn't matter for Flask's built-in server?

Also, Werkzeug's documentation on Fixers warns "Do not use this middleware in non-proxy setups for security reasons." Considering the fix was clearly necessary, can I assume I'm on a proxy setup?

3

There are 3 best solutions below

0
On

Making the assumption that what you mean by "virtual machine using its native http server" is that you're running something like nginx as a reverse proxy/http server on the same VM, and then pointing it to localhost:4000. And that the error you were getting was similar to the ModuleNotFoundError error in this thread. AND you're accessing the app from a different computer by going to the VM's IP address: you need to use ProxyFix, as nginx is acting as a (reverse) proxy server for the http requests, connecting to localhost, and then sending whatever it spits out to the client that hit nginx.

If you're not doing that, and your app has its own native http server, you can change how gunicorn is bound and set it to -b 0.0.0.0:4000 to enable remote connections. This binds it to all available IP addresses, so if your VM has multiple it will respond on any of them if a request is sent to port 4000. If you only want it to listen on a specific address, you can enter that there instead. Setting -b 127.0.0.1:4000 means that it will only listen to connections from localhost on port 4000, which makes it pretty useless as a webapp on a VM.

But if this is being exposed to the internet, you shouldn't do this. You should do the first thing.

The warning about not using the middleware if you're not using a proxy is applicable here. proxy_fix needs to know how many proxies are in front of it, so that it doesn't blindly trust headers that are being sent to it from potentially unwanted/malicious devices in the middle of your request and the gunicorn server serving that request. Setting the values provided to higher than the number of proxies in front of your gunicorn server also allows for faked headers to be passed to gunicorn as well. This probably isn't a problem in your case, but it is something you want to understand if you're going to put this app on the internet.

0
On

You need to show the code that defines your Flask application "app".

Where is "app" defined? Are you importing it from another file?

Here is my attempt to reproduce the issue:

$ cat myproject.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "ok"

$ bin/gunicorn -w 4 -b 127.0.0.1:4000 myproject:app &
[1] 27435

2014-03-04 12:18:36 [27435] [INFO] Starting gunicorn 18.0
2014-03-04 12:18:36 [27435] [INFO] Listening at: http://127.0.0.1:4000 (27435)
2014-03-04 12:18:36 [27435] [INFO] Using worker: sync
2014-03-04 12:18:36 [27441] [INFO] Booting worker with pid: 27441
2014-03-04 12:18:36 [27442] [INFO] Booting worker with pid: 27442
2014-03-04 12:18:36 [27445] [INFO] Booting worker with pid: 27445
2014-03-04 12:18:36 [27448] [INFO] Booting worker with pid: 27448

$ curl http://127.0.0.1:4000/
ok

As you can see it works fine. You definitely don't need ProxyFix in this case.

0
On

A little late to the party, but here (Flask v1) is what the documentation says about ProxyFix.

Documentation for v2

To paraphrase: Deploying your server using gunicorn behind an HTTP proxy you will need to rewrite some of the headers so that the application can work. And Werkzeug ships with a fixer that will solve some of the common setups.

from werkzeug.middleware.proxy_fix import ProxyFix

app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)