Semplificato eccessivamente: è necessario qualcosa che esegua Python ma Python non è il migliore nel gestire tutti i tipi di richieste.
[dichiarazione di non responsabilità: sono uno sviluppatore di Gunicorn]
Meno semplificato: indipendentemente dal server di app che usi (Gunicorn, mod_wsgi, mod_uwsgi, cherrypy) qualsiasi tipo di distribuzione non banale avrà qualcosa a monte che gestirà le richieste che la tua app Django non dovrebbe gestire. Esempi fondamentali di tali richieste sono al servizio di risorse statiche (images / css / js).
Ciò si traduce in due primi livelli della classica "architettura a tre livelli". Vale a dire, il server web (Nginx nel tuo caso) gestirà molte richieste di immagini e risorse statiche. Le richieste che devono essere generate dinamicamente verranno quindi passate al server delle applicazioni (Gunicorn nel tuo esempio). (A parte il terzo dei tre livelli è il database)
Storicamente parlando, ciascuno di questi livelli sarebbe ospitato su macchine separate (e molto probabilmente ci sarebbero più macchine nei primi due livelli, ovvero: 5 server web inviano richieste a due server di app che a loro volta interrogano un singolo database).
Nell'era moderna ora abbiamo applicazioni di tutte le forme e dimensioni. Non tutti i progetti di fine settimana o siti di piccole imprese hanno effettivamente bisogno della potenza di più macchine e funzioneranno abbastanza felicemente su una singola scatola. Ciò ha generato nuove voci nell'array di soluzioni di hosting. Alcune soluzioni sposano l'app server con il web server (Apache httpd + mod_wsgi, Nginx + mod_uwsgi, ecc.). E non è affatto raro ospitare il database sullo stesso computer di una di queste combinazioni di server web / app.
Ora, nel caso di Gunicorn, abbiamo preso una decisione specifica (copiando dall'unicorno di Ruby) per mantenere le cose separate da Nginx facendo affidamento sul comportamento di Nginx. In particolare, se possiamo supporre che Gunicorn non leggerà mai le connessioni direttamente da Internet, non dovremo preoccuparci dei client lenti. Ciò significa che il modello di elaborazione per Gunicorn è imbarazzantemente semplice.
La separazione consente inoltre di scrivere Gunicorn in puro Python, riducendo al minimo i costi di sviluppo senza influire in modo significativo sulle prestazioni. Inoltre, consente agli utenti di utilizzare altri proxy (supponendo che si bufferizzino correttamente).
Per quanto riguarda la tua seconda domanda su ciò che gestisce effettivamente la richiesta HTTP, la risposta semplice è Gunicorn. La risposta completa è che Nginx e Gunicorn gestiscono la richiesta. Fondamentalmente, Nginx riceverà la richiesta e, se si tratta di una richiesta dinamica (generalmente basata su pattern URL), fornirà tale richiesta a Gunicorn, che la elaborerà e quindi restituirà una risposta a Nginx che quindi inoltra la risposta all'originale cliente.
Quindi in chiusura sì. Hai bisogno di Nginx e Gunicorn (o qualcosa di simile) per una corretta distribuzione di Django. Se stai specificatamente cercando di ospitare Django con Nginx, indagherei Gunicorn, mod_uwsgi e forse CherryPy come candidati per il lato Django delle cose.