How to run Gunicorn upstream with an Nginx SSL configuration?

I have an nginx server with an SSL certificate installed. I want to pass any requests upstream to my Gunicorn server running at 0.0.0.0:8000. However, whenever I run the Gunicorn server, it gives me an error saying that there's too many redirect loops. If I run gunicorn over https, then the connection WILL become secure, but it won't connect to the gunicorn server and it'll just say bad gateway. Also, here is the error I get when attempting to connect while running gunicorn with https:

Traceback (most recent call last):
  File "/opt/bitnami/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 515, in spawn_worker
    worker.init_process()
  File "/opt/bitnami/python/lib/python2.7/site-packages/gunicorn/workers/base.py", line 126, in init_process
    self.run()
  File "/opt/bitnami/python/lib/python2.7/site-packages/gunicorn/workers/sync.py", line 119, in run
    self.run_for_one(timeout)
  File "/opt/bitnami/python/lib/python2.7/site-packages/gunicorn/workers/sync.py", line 66, in run_for_one
    self.accept(listener)
  File "/opt/bitnami/python/lib/python2.7/site-packages/gunicorn/workers/sync.py", line 30, in accept
    self.handle(listener, client, addr)
  File "/opt/bitnami/python/lib/python2.7/site-packages/gunicorn/workers/sync.py", line 141, in handle
    self.handle_error(req, client, addr, e)
  File "/opt/bitnami/python/lib/python2.7/site-packages/gunicorn/workers/base.py", line 213, in handle_error
    self.log.exception("Error handling request %s", req.uri)
AttributeError: 'NoneType' object has no attribute 'uri'
[2016-01-01 15:37:45 +0000] [935] [INFO] Worker exiting (pid: 935)
[2016-01-01 20:37:45 +0000] [938] [INFO] Booting worker with pid: 938
[2016-01-01 15:37:46 +0000] [938] [ERROR] Exception in worker process:

Here is my nginx configuration:

  server {
    # port to listen on. Can also be set to an IP:PORT
    listen 80;
    listen 443 ssl;
    ssl_certificate /etc/ssl/pyhub_crt.crt;
    ssl_certificate_key /etc/ssl/pyhub.key;
    server_name www.pyhub.co;
    server_name_in_redirect off;
    access_log /opt/bitnami/nginx/logs/access.log;
    error_log /opt/bitnami/nginx/logs/error.log;
    location /E0130777F7D5B855A4C5DEB138808515.txt {
        root /home/bitnami;
    }

    location / {
    proxy_pass_header Server;
    proxy_set_header Host $host;
    proxy_set_header X-Scheme $scheme;
    proxy_set_header X-SSL-Protocal $ssl_protocol;
    proxy_connect_timeout 10;
    proxy_read_timeout 10;
    proxy_redirect http:// $scheme://;
    proxy_pass http://localhost:8000;
    }

Solution 1:

If gunicorn is bound to 0.0.0.0 it is bound to all interfaces, therefore it is already exposed to the outside interface. If nginx tries to bind to the any interface on the same port it will fail.

Gunicorn should be bound to a specific ip or better 127.0.0.1 so it is only bound to the internal ip.

Sesond, you say you want to pass https to gunicorn, but the traffic is protected with SSL to your proxy which has the certificates, that is ngninx. After that, the traffic is in clear internally (i.e. it is http) to gunicorn, unless you also have SSL setup on gunicorn.

So, your nginx config should have:

  • An upstream server to gunicorn with ip 127.0.0.1 port 8080 or whatever you want.
  • A server directive listening on port 80 for http and 443 for https
  • a proxy-pass directive in the server bloc to forward to upstream

my nginx proxy config for SSL is something like this:

upstream website    {
    ip_hash;                        # for sticky sessions, more below
    server                          website:8000 max_fails=1 fail_timeout=10s;
}

server {
    # only listen to https here
    listen                          443 ssl http2;
    listen                          [::]:443 ssl http2;
    server_name                     yourdomain.here.com;

    access_log                      /var/log/nginx/yourdomain.here.com.access.log;
    error_log                       /var/log/nginx/yourdomain.here.com.error.log;
    ssl                             on;
    ssl_certificate                 /etc/nginx/certs/ca-cert.chained.crt;
    ssl_certificate_key             /etc/nginx/certs/cert.key;
    ssl_session_cache               shared:SSL:5m;
    ssl_session_timeout             10m;
    ssl_protocols                   TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers       on;
    #ssl_dhparam                     /etc/nginx/certs/dhparams.pem;
    # use the line above if you generated a dhparams file 
    ssl_ciphers                     'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_buffer_size                 8k;

    location / {
        proxy_pass                  http://website;
        proxy_set_header            Host $host;
        proxy_set_header            X-Real-IP $remote_addr;
        proxy_http_version          1.1;

        proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header            X-Forwarded-Proto http;
        proxy_redirect              http:// $scheme://;
    }
}

# redirect http to https here
server {
    listen                          80;
    listen                          [::]:80;
    server_name                     yourdomain.here.com;
    return                          301 https://$server_name/;
}

Solution 2:

Try this:

upstream app_server {
    server 127.0.0.1:8000 fail_timeout=0;
}

server {
    # port to listen on. Can also be set to an IP:PORT
    listen 80;
    listen 443 ssl;

    ssl_certificate /etc/ssl/pyhub_crt.crt;
    ssl_certificate_key /etc/ssl/pyhub.key;

    server_name www.pyhub.co;
    server_name_in_redirect off;

    access_log /opt/bitnami/nginx/logs/access.log;
    error_log /opt/bitnami/nginx/logs/error.log;

    location /E0130777F7D5B855A4C5DEB138808515.txt {
        root /home/bitnami;
    }

    location / {
        try_files @proxy_to_app;
    }

    location @proxy_to_app {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        proxy_pass http://app_server;

        proxy_pass_header Server;
        proxy_set_header Host $host;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-SSL-Protocol $ssl_protocol;
    }
}

Note that this will (properly) terminate SSL at nginx rather than gunicorn. You also misspelled Protocal in X-SSL-Protocal; fixed in my example :)

See here for canonical information on deploying gunicorn with nginx.

Solution 3:

I should have posted this answer a long time ago, as I've had my site working for quite a while now. But here's how I got my configuration to work.

After fiddling around a lot with the Nginx configuration, I got pretty frustrated and gave up. So, I deleted that server, made a new one, with fresh installations of everything, and cloned the source code for my website from the repository that hosts it. After doing this, I used this Nginx configuration for it to work:

  upstream djangosite {
    server 127.0.0.1:8000 fail_timeout=2s;
  }
  server {
    # port to listen on. Can also be set to an IP:PORT
    listen 443 ssl;
    server_name pyhub.co;
    ssl_certificate /etc/ssl/pyhub.crt;
    ssl_certificate_key /etc/ssl/pyhub.key;
    location / {
      proxy_pass http://djangosite;
    }
  server {
    listen 80;
    server_name pyhub.co;
    return 301 https://$server_name$request_uri;
  }

To this day I still don't know what was causing me problems with my configuration, or why I had to get another server with a completely fresh install on it, but I'm just glad it works. I would select one of the two excellent answers already provided, but none of them gave me the solution, and I don't want to select my own, because I believe it is too specific of a problem that I had and selecting this answer might not be able to help many people in the future.