Dumping Apache2 and mod_wsgi for Gunicorn & NGINX: The Missing Guide for Getting Up & Running with Python + Flask in Production

This blog post is brought to you by the developer of BitBudget. BitBudget is an automated budgeting app for Android and iOS which syncs with your bank account and helps you avoid overspending. If you’d like to quit living paycheck-to-paycheck and get a better handle on your finances, download it today! https://bitbudget.io

Well hello there internet friend. Having trouble getting your Flask app up and running in production? This can be quite the pain! In the past I’ve used Apache2 and mod_wsgi to serve my Flask apps, but getting it all setup tends to be a serious headache. It doesn’t help that the official docs on the Flask website are pretty thin, and don’t really list all of the steps you need to get everything working. Furthermore, a lot of the blog posts on the topic of Apache2, mod_wsgi, and Flask don’t always work either.

And that’s what brings me here to WordPress tonight to write this blog post. I’m currently working on pushing a new project to production, and well… I can’t seem to get Apache to play nice with my Flask app! (even though I’ve already done this before in the past). And I’m not sure exactly what’s causing my issues. Is it the switch from Ubuntu 16 to Ubuntu 18? The newest version of Apache2 doesn’t work with the old configuration file? I just don’t know. All I do know is: there has to be a better way!

So after several hours of fumbling around with Apache, I decided that most of the people in the Flask community probably aren’t using Apache, so maybe a good first step would be to try and pick a more common setup: Gunicorn + NGINX.

If you happen to have stumbled upon this blog post trying to get Apache and mod_wsgi to work with Flask, I suggest you simply ditch Apache and use Gunicorn + NGINX instead. Specifically, I recommend you follow along with this excellent tutorial which I just discovered from DigitalOcean: How To Serve Flask Applications with Gunicorn and Nginx on Ubuntu 18.04

In fact, the real purpose of this blog post is that I want to bookmark that tutorial so I don’t lose it! The guide is from the people at DigitalOcean, but the steps described should work with any Linux Virtual Server. But before you get started following that guide, there are a few prerequisites steps:

  1. Setup a non-root user with sudo privileges on your server
  2. Install NGINX
  3. Buy a domain name and point it at your server

But before following along with the DigitalOcean guide above, please note that there may be an error in the guide related to the myproject.service file that you’ll be instructed to create. For me, gunicorn doesn’t seem to install into my python virtual environment so I had to install it globally:

$ sudo apt install gunicorn

And then change ExecStart line in myproject.service to:

ExecStart=gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app

And one last note, towards the end of the guide above you are going to need to specify how many worker processes you want serving up your Flask app. I had absolutely no idea how many to specify as this was not something I had to worry about in the past when setting up Apache and Flask. According to gunicorn’s documentation a rule of thumb is (2 * numberOfServerCores) + 1. So if that part of the guide sticks out when you get to it, go ahead and use that rule of thumb for estimating how many worker processes you want to provision. And that’s it!

I know it’s a lot of steps to get all of this up and running. But I do think that the guide I’ve bookmarked here in this post is the best I’ve found. However, if you don’t have any experience with Linux and some of this stuff is over your head, there are other options. But if you’re like me and you do have a fair amount of experience with that kind of stuff, but are just stuck getting Apache configured, ditch Apache/mod_wsgi and go with Gunicorn/NGINX.

UPDATE (June 1st, 2020): One thing I forgot to mention is how to get Gunicorn and NGINX to reload changes that you make to your source code. If you push a new version of your app to the server, you can use the following command(s) to make sure the changes are served:

Restart Gunicorn

$ sudo systemctl restart myproject

Restart NGINX (if necessary)

$ sudo systemctl restart nginx

Please note that the name “myproject” is what the authors of the tutorial mentioned above called their project in the example, so for consistency I’ve used the same name. Replace ‘myproject’ with whatever project name you chose while following along with the DigitalOcean tutorial.

UPDATE (More Notes to Self): If you need to install dependencies on the production server and are using a virtual environment as described in the tutorial, log into the server using SSH as a non-root user, navigate to your project directory, activate the virtual environment, and then install your dependencies:

$ cd ~/myproject

$ source myprojectenv/bin/activate

(myprojectenv) $ pip install supersweetlib.py

Or if you have a requirements.txt file…

(myprojectenv) $ pip install -r requirements.txt

UPDATE (April 17th, 2022): The last time I tried deploying a production flask app using the steps described in this blog post along with the DigitalOcean guide, I kept running into a bunch of problems with gunicorn. It looks like my issue was the gunicorn was not actually getting installed into my virtual environment so the ExecStart command in myprojectname.service was never able to actually start up gunicorn. To fix this issue, when you install gunicorn in your virtual environment make sure to use the -I (that’s a capital i, not a lowercase L) or –ignore-installed flag to ensure that gunicorn actually gets installed into your virtual environment:

(venv) $ pip install -I gunicorn