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.