0
votes

I'm trying to deploy my Django app with Nginx and Gunicorn by following this tutorial, but I modified some steps so I can use Conda instead of ViritualEnv.

The setup looks like this:

  1. Nginx replies with my Vue app
  2. Requests from Vue are made to api.example.com
  3. Nginx listens to api.example.com and directs requests to Gunicorn's unix socket

Things I've checked:

  1. I can see the Vue requests in Nginx's access.log.
  2. I can also see those requests with journalctl -f -u gunicorn, in the supervisor.log, and gunicorn's access.log
  3. When my Django app starts, it's creates a log file, so I can see that Gunicorn starts it. But Django is not responding to requests from the unix socket.
  4. I can see a response from Django when I ssh in and run the following command: curl --no-buffer -XGET --unix-socket /var/www/example/run/gunicorn.sock http://localhost/about. This command only gives a response when any of my ALLOWED_HOSTS are used in place of localhost.
  5. My Nginx, Supervisor and Gunicorn configurations all use the full path to gunicorn.sock.

Should I see Django running on port 8000 or anything if I do something like nmap localhost? I saw another post mention that Nginx should point to port 8000 and that gunicorn should be run with either:

  1. gunicorn --bind 0.0.0.0:8000 <djangoapp>.wsgi --daemon
  2. gunicorn <djangoapp>.wsgi:application --bind <IP>:8000 --daemon
  3. gunicorn <djangoapp>.wsgi:application --bind=unix:/var/www/example/run/gunicorn.sock

But doesn't exposing port 8000 defeat the purpose of using Nginx as a reverse proxy and Gunicorn's unix socket? Doesn't exposing 8000 also increase the surface area for attack vectors? Or is it best practice to expose port 8000? I'm a bit confused why I would use both expose that port and use both Nginx and Gunicorn.

My main problem: Why can I get responses from Django via the unix socket with curl, but not via requests from Vue? Why aren't Vue's requests making it from Gunicorn to Django via the unix socket?

I'm really stuck. Any suggestions?

Frontend Nginx config

server {
        listen 80 default_server;
        listen [::]:80 default_server;
        # server_name example.com;
        # server_name myIP;
        root /var/www/example/frontend/dist;
        server_name example.com www.example.com;

        location =/robots.txt {
                root /opt/example;
        }

        location /thumbnail/ {
                alias /opt/example/static/img/thumbnail/;
        }

        location /bg/ {
                alias /opt/example/static/img/bg/;
        }

        location / {
                try_files $uri $uri/ /index.html;
        }
}

API Nginx config

upstream backend_server {
        server unix:/var/www/example/run/gunicorn.sock fail_timeout=0;
}

server {
        listen 80;
        server_name api.example.com
        client_max_body_size 4G;

        access_log /var/log/nginx/api-access.log;
        error_log /var/log/nginx/api-error.log;

        location / {
                include proxy_params;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $host;
                proxy_headers_hash_max_size 512;
                proxy_headers_hash_bucket_size 128; 
                proxy_redirect off;
                if (!-f $request_filename) {
                        proxy_pass http://backend_server;
                }
        }
}

Gunicorn config

#!/bin/bash

NAME=”backend”
DJANGODIR=/var/www/example/backend
SOCKFILE=/var/www/example/run/gunicorn.sock
USER=django
GROUP=example
NUM_WORKERS=3
DJANGO_SETTINGS_MODULE=backend.settings
DJANGO_WSGI_MODULE=backend.wsgi
CONDA_SRC=/home/justin/anaconda3/etc/profile.d/conda.sh
GUNICORN=/home/justin/anaconda3/envs/production/bin/gunicorn

echo “starting backend”

cd $DJANGODIR
    source $CONDA_SRC
conda activate production
    export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
    export PYTHONPATH=$DJANGODIR:$PYTHONPATH
    
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR

exec $GUNICORN
 ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $NUM_WORKERS \
  --user=$USER --group=$GROUP \
  --bind=unix:$SOCKFILE \
  --log-level=debug \
  --log-file=- \
  --error-logfile=/var/www/example/backend/logs/gunicorn-error.log \
  --access-logfile=/var/www/example/backend/logs/gunicorn-access.log

Gunicorn access.log

- - [08/Sep/2020:01:51:24 -0400] "OPTIONS /c/about/ HTTP/1.0" 200 0 "http://example.com/c/about" "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Mobile Safari/537.36"
- - [08/Sep/2020:01:51:24 -0400] "POST /c/about/ HTTP/1.0" 400 143 "http://example.com/c/about" "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Mobile Safari/537.36"
2
how does your nginx config look like?yedpodtrzitko
@yedpodtrzitko I just updated the post with my nginx configs.Soubriquet

2 Answers

0
votes

But doesn't exposing port 8000 defeat the purpose of using Nginx as a reverse proxy and Gunicorn's unix socket?

In gunicorn, you have to expose 8000 port on localhost like this gunicorn --bind 127.0.0.1:8000 <djangoapp>.wsgi --daemon. Exposing it on 0.0.0.0 will obviously be a security vulnerability considering your nginx in on the same server.

Doesn't exposing 8000 also increase the surface area for attack vectors? Or is it best practice to expose port 8000? I'm a bit confused why I would use both expose that port and use both Nginx and Gunicorn.

You don't need to expose port 8000 you can expose any port but you need to tell gunicon to listen on at least a single port so that nginx can pass requests to it.

And regarding using both nginx and gunicorn, they both are really different and handle very different use case/functions of an application.

Nginx uses "event‑driven" approach to handle requests so a single worker of nginx can handle 1000s of req simultaneously. But Gunicorn on the other hand mostly(by default) uses sync worker which means a request will remain with a worker till it is processed. (posted this twice today :p)

So you need both if you remove nginx all your requests will return 50X except which are currently handled by gunicorn until the worker is free. And also gunicorn is not made to handle user traffic or in bigger application things like load balancing can only be done by nginx. So nginx has it's own purpose in an application.

0
votes

After neeraj9194 pointed out the 400, I did more searching for issues relating to Nginx, Gunicorn 400 and Django and I came across a ton of similar issues. Looks like it's mainly an Nginx issue. The answer in this blog fixed my issue.

I replaced the location block in my API Nginx config with:

location / {
      proxy_set_header Host $host;
      proxy_pass http://backend_server;
      proxy_set_header X-Forwarded-Host $server_name;
      proxy_set_header X-Real-IP $remote_addr;
  }