73

We've got a few Django setups that go through a proxy (Apache and Nginx) that eventually make their way to the actual Django runtime.

We need to have HTTPS end to end even once it's in our network. We've been revisiting Gunicorn due to its success and performance in our other setups, but needed to test with HTTPS end to end to be consistent.

Our topology is as such:

https://foo.com -> [Public facing proxy] -> (https) -> [internal server https://192...:8001]

How does one configure Gunicorn to listen on HTTPS with a self signed certificate?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
dmyung
  • 7,959
  • 5
  • 27
  • 22

5 Answers5

139

Gunicorn now supports SSL, as of version 17.0. You can configure it to listen on https like this:

$ gunicorn --certfile=server.crt --keyfile=server.key test:app

If you were using --bind to listen on port 80, remember to change the port to 443 (the default port for HTTPS connections). For example:

$ gunicorn --certfile=server.crt --keyfile=server.key --bind 0.0.0.0:443 test:app
Mark Amery
  • 143,130
  • 81
  • 406
  • 459
GregM
  • 1,818
  • 1
  • 13
  • 18
  • 1
    Can you point me at the relevant documents for how to do this? I'm deploying on heroku so i'm unsure what my "server.crt" for instance. – Drew Verlee Jan 11 '13 at 17:46
  • 3
    @DrewV If you're deploying on heroku there's no need to support SSL yourself - heroku does it for you. See [their docs](https://devcenter.heroku.com/articles/ssl-endpoint) for help. You will need to configure gunicorn to honor heroku's secure header configuration. I use something like this in my config file: `forwarded_allow_ips = '*'` and `secure_scheme_headers = {'X-FORWARDED-PROTO': 'https',}` – GregM Jan 11 '13 at 19:21
  • 1
    What is the `test:app` part of the end? – Mikeumus Dec 21 '15 at 21:41
  • Running this for me returns `AttributeError: 'NoneType' object has no attribute 'uri'` – Dorian Dore Dec 30 '15 at 02:58
  • Exactly what I needed! I added these settings to my `gunicorn_start.bash` file and restarted `supervisor`. Now everything works just fine. – wcyn Jul 12 '16 at 04:24
  • @GregM how do you know the traffic is secure between all hops public facing <--> application server? – Cochise Ruhulessin Dec 03 '16 at 02:38
  • OMG forgot the port 443! – imbr Nov 05 '21 at 01:06
29

Massively late reply, but for anyone else coming across this, there's another option using nginx as the "[Public facing proxy]" above.

Configure nginx to handle the incoming SSL traffic on port 443, and then proxy_pass to gunicorn on an internal port. External traffic is encrypted, and the traffic between nginx and gunicorn isn't exposed anyway. I find this very simple to manage.

mafrosis
  • 2,720
  • 1
  • 25
  • 34
  • How do you know it isn't exposed? – Cochise Ruhulessin Dec 03 '16 at 02:39
  • 16
    Because you bind gunicorn to 127.0.0.1, such that only localhost can access it – mafrosis Dec 04 '16 at 09:36
  • 7
    Downside of this is it breaks Django's `build_absolute_uri()` function; Django will see the HTTP protocol and build the URI using that scheme, even if the original request was with HTTPS. – robbrit May 13 '20 at 23:17
  • 3
    @robbrit you can set a header to avoid that issue. See the [SECURE_PROXY_SSL_HEADER](https://docs.djangoproject.com/en/3.2/ref/settings/#secure-proxy-ssl-header) docs. – Alasdair Sep 04 '21 at 14:59
  • Given people are still reading this answer - in 2021 I suggest you don't use nginx and instead check out Caddy server – mafrosis Sep 05 '21 at 06:38
9

If you're using a gunicorn.config.py or similar gunicorn config file you can add the certificate file and key file.

certfile = '/etc/letsencrypt/live/example.com/fullchain.pem'
keyfile = '/etc/letsencrypt/live/example.com/privkey.pem'

Config files can be used to initialise settings as env variables and can be helpful if you had lots of settings. To use config file

  • Create a config file by creating a file named gunicorn.config.py

  • Some usual settings would be

      bind = "0.0.0.0:8000"
      workers = 4
      pidfile = 'pidfile'
      errorlog = 'errorlog'
      loglevel = 'info'
      accesslog = 'accesslog'
      access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
    

    and of course

      certfile = '/etc/letsencrypt/live/example.com/fullchain.pem'
      keyfile = '/etc/letsencrypt/live/example.com/privkey.pem'
    

Check out the documentation and a config file example

to run gunicorn with these settings

    $ gunicorn app:app

since

By default, a file named gunicorn.conf.py will be read from the same directory where gunicorn is being run.

greysou1
  • 346
  • 3
  • 8
4

In addition to certfile and keyfile you need to add ca-certs as well. Without passing ca-certs, I was getting Trust anchor for certification path not found. on Android devices.

Sample command:

/usr/bin/python3 /usr/local/bin/gunicorn --bind 0.0.0.0:443 wsgi:app --workers=8 --access-logfile=/root/app/logs/access.log --error-logfile=/root/app/logs/error.log --certfile=/root/app/certificate.crt --keyfile=/root/app/private.key --ca-certs=/root/app/ca_bundle.crt --daemon
Gaurav Singla
  • 1,405
  • 1
  • 17
  • 17
0

An alternative approach is to make use of a secure tunnelling service like ngrok. With ngrok, you can easily tunnel from your gunicorn server to a https enabled endpoint they provide. Depending on your purpose, this could be a convenient workaround.

It's very very easy to use - Personally I find nginx somewhat complicated for first-time use.

Using ngrok is as simple as (after installing):

gunicorn -b 0.0.0.0:4000
ngrok http 4000

This provides you with a secure endpoint like https://abcd.ngrok.io that you can send requests to, just the way you'd send requests to your gunicorn server.

So http://0.0.0.0:4000/hello becomes https://abcd.ngrok.io/hello.