No matter which application server you use (Gunicorn, mod_wsgi, mod_uwsgi, cherrypy), any non-trivial deployment will have something at the top that will handle requests that your Django application should not handle. Trivial examples of such queries are for static resources (images / css / js).
This leads to the first two levels of the classic "three-tier architecture." That is, the web server (Nginx in your case) will handle many requests for images and static resources. Requests that should be dynamically generated are then sent to the application server (Gunicorn in your example). (Aside, the third of three levels is the database)
Historically, each of these levels will be hosted on separate machines (and in the first two levels, most likely there will be several machines, that is: 5 web servers send requests to two application servers, which, in turn, request one database).
In the modern era, we now have applications of all shapes and sizes. Not every weekend or small business project actually needs the horsepower of several cars and will work quite happily on one box. This led to the emergence of new entries in the array of hosting solutions. Some solutions will go beyond the application server to the web server (Apache httpd + mod_wsgi, Nginx + mod_uwsgi, etc.). And itβs not at all uncommon to place a database on the same computer as one of these server / application combinations.
Now, in the case of Gunicorn, we made a specific decision (copying from Ruby Unicorn) to separate things from Nginx, relying on the Nginx proxy behavior. In particular, if we can assume that Gunicorn will never read connections directly from the Internet, then we need not worry about slow clients. This means that the processing model for Gunicorn is awkwardly simple.
Separation also allows you to write Gunicorn in pure Python, which minimizes development costs without significantly affecting performance. It also allows users to use other proxies (provided they are buffered correctly).
As for your second question about what actually handles the HTTP request, the simple answer is Gunicorn. Full answer: Nginx and Gunicorn process the request. Basically, Nginx will receive the request, and if it is a dynamic request (usually based on URL patterns), then it will provide this request to Gunicorn, which will process it, and then return a Nginx response, which then redirects the response back to the original client.
So in conclusion, yes. For proper Django / flask deployment, you need both Nginx and Gunicorn (or something similar). If you specifically want to host Django / flask with Nginx, I would examine Gunicorn, mod_uwsgi, and possibly CherryPy as candidates for the Django / flask side.
Renouncement